/*
 * Copyright 1999-2101 Alibaba Group.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.fastjson.serializer;

import static com.alibaba.fastjson.parser.CharTypes.replaceChars;

import java.io.IOException;
import java.io.Writer;

import com.alibaba.fastjson.parser.CharTypes;

/**
 * 
 * @author wenshao<szujobs@hotmail.com>
 * 
 */
public final class SerializeWriter extends Writer {
	/**
	 * The buffer where data is stored.
	 */
	protected char buf[];

	/**
	 * The number of chars in the buffer.
	 */
	protected int count;

	private final static ThreadLocal<char[]> bufLocal = new ThreadLocal<char[]>();

	/**
	 * Creates a new CharArrayWriter.
	 */
	public SerializeWriter() {
		buf = bufLocal.get(); // new char[1024];
		if (buf == null) {
			buf = new char[1024];
		} else {
			bufLocal.set(null);
		}
	}
	
	/**
	 * Creates a new CharArrayWriter with the specified initial size.
	 * 
	 * @param initialSize
	 *            an int specifying the initial buffer size.
	 * @exception IllegalArgumentException
	 *                if initialSize is negative
	 */
	public SerializeWriter(int initialSize) {
		if (initialSize < 0) {
			throw new IllegalArgumentException("Negative initial size: " + initialSize);
		}
		buf = new char[initialSize];
	}

	/**
	 * Writes a character to the buffer.
	 */
	public void write(int c) {
		int newcount = count + 1;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		buf[count] = (char) c;
		count = newcount;
	}

	public void write(char c) {
		int newcount = count + 1;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		buf[count] = c;
		count = newcount;
	}

	/**
	 * Writes characters to the buffer.
	 * 
	 * @param c
	 *            the data to be written
	 * @param off
	 *            the start offset in the data
	 * @param len
	 *            the number of chars that are written
	 */
	public void write(char c[], int off, int len) {
		if (off < 0 || off > c.length || len < 0 || off + len > c.length || off + len < 0) {
			throw new IndexOutOfBoundsException();
		} else if (len == 0) {
			return;
		}

		int newcount = count + len;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		System.arraycopy(c, off, buf, count, len);
		count = newcount;

	}

	public void expandCapacity(int minimumCapacity) {
		int newCapacity = (buf.length + 1) * 2;
		if (newCapacity < minimumCapacity) {
			newCapacity = minimumCapacity;
		}
		char newValue[] = new char[newCapacity];
		System.arraycopy(buf, 0, newValue, 0, count);
		buf = newValue;
 	}

	/**
	 * Write a portion of a string to the buffer.
	 * 
	 * @param str
	 *            String to be written from
	 * @param off
	 *            Offset from which to start reading characters
	 * @param len
	 *            Number of characters to be written
	 */
	public void write(String str, int off, int len) {
		int newcount = count + len;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		str.getChars(off, off + len, buf, count);
		count = newcount;
	}

	/**
	 * Writes the contents of the buffer to another character stream.
	 * 
	 * @param out
	 *            the output stream to write to
	 * @throws IOException
	 *             If an I/O error occurs.
	 */
	public void writeTo(Writer out) throws IOException {
		out.write(buf, 0, count);
	}

	public SerializeWriter append(CharSequence csq) {
		String s = (csq == null ? "null" : csq.toString());
		write(s, 0, s.length());
		return this;
	}

	public SerializeWriter append(CharSequence csq, int start, int end) {
		String s = (csq == null ? "null" : csq).subSequence(start, end).toString();
		write(s, 0, s.length());
		return this;
	}

	/**
	 * Appends the specified character to this writer.
	 * 
	 * <p>
	 * An invocation of this method of the form <tt>out.append(c)</tt> behaves
	 * in exactly the same way as the invocation
	 * 
	 * <pre>
	 * out.write(c)
	 * </pre>
	 * 
	 * @param c
	 *            The 16-bit character to append
	 * 
	 * @return This writer
	 * 
	 * @since 1.5
	 */
	public SerializeWriter append(char c) {
		write(c);
		return this;
	}

	/**
	 * Resets the buffer so that you can use it again without throwing away the
	 * already allocated buffer.
	 */
	public void reset() {
		count = 0;
	}

	/**
	 * Returns a copy of the input data.
	 * 
	 * @return an array of chars copied from the input data.
	 */
	public char toCharArray()[] {
		char[] newValue = new char[count];
		System.arraycopy(buf, 0, newValue, 0, count);
		return newValue;
	}

	/**
	 * Returns the current size of the buffer.
	 * 
	 * @return an int representing the current size of the buffer.
	 */
	public int size() {
		return count;
	}

	/**
	 * Converts input data to a string.
	 * 
	 * @return the string.
	 */
	public String toString() {
		return new String(buf, 0, count);
	}

	/**
	 * Flush the stream.
	 */
	public void flush() {
	}

	/**
	 * Close the stream. This method does not release the buffer, since its
	 * contents might still be required. Note: Invoking this method in this
	 * class will have no effect.
	 */
	public void close() {
		bufLocal.set(buf);
	}
	
	public void writeBooleanArray(boolean[] array) throws IOException {
		int[] sizeArray = new int[array.length];
		int totalSize = 2;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				totalSize++;
			}
			boolean val = array[i];
			int size;
			if (val ) {
				size = 4; //"true".length();
			} else {
				size = 5; //"false".length();
			}
			sizeArray[i] = size;
			totalSize += size;
		}

		int newcount = count + totalSize;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		buf[count] = '[';

		int currentSize = count + 1;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				buf[currentSize++] = ',';
			}

			boolean val = array[i];
			if (val) {
				//System.arraycopy("true".toCharArray(), 0, buf, currentSize, 4);
				buf[currentSize++] = 't';
				buf[currentSize++] = 'r';
				buf[currentSize++] = 'u';
				buf[currentSize++] = 'e';
			} else {
				buf[currentSize++] = 'f';
				buf[currentSize++] = 'a';
				buf[currentSize++] = 'l';
				buf[currentSize++] = 's';
				buf[currentSize++] = 'e';
			}
		}
		buf[currentSize] = ']';

		count = newcount;
	}

	public void writeInt(int i) throws IOException {
		if (i == Integer.MIN_VALUE) {
			write("-2147483648");
			return;
		}

		int size = (i < 0) ? IOUtils.stringSize(-i) + 1 : IOUtils.stringSize(i);

		int newcount = count + size;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		IOUtils.getChars(i, newcount, buf);

		count = newcount;
	}
	
	public void writeShortArray(short[] array) throws IOException {
		int[] sizeArray = new int[array.length];
		int totalSize = 2;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				totalSize++;
			}
			short val = array[i];
			int size = IOUtils.stringSize(val);
			sizeArray[i] = size;
			totalSize += size;
		}

		int newcount = count + totalSize;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		buf[count] = '[';

		int currentSize = count + 1;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				buf[currentSize++] = ',';
			}

			short val = array[i];
			currentSize += sizeArray[i];
			IOUtils.getChars(val, currentSize, buf);
		}
		buf[currentSize] = ']';

		count = newcount;
	}
	
	public void writeByteArray(byte[] array) throws IOException {
		int[] sizeArray = new int[array.length];
		int totalSize = 2;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				totalSize++;
			}
			byte val = array[i];
			int size = IOUtils.stringSize(val);
			sizeArray[i] = size;
			totalSize += size;
		}

		int newcount = count + totalSize;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		buf[count] = '[';

		int currentSize = count + 1;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				buf[currentSize++] = ',';
			}

			byte val = array[i];
			currentSize += sizeArray[i];
			IOUtils.getChars(val, currentSize, buf);
		}
		buf[currentSize] = ']';

		count = newcount;
	}

	public void writeIntArray(int[] array) throws IOException {
		int[] sizeArray = new int[array.length];
		int totalSize = 2;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				totalSize++;
			}
			int val = array[i];
			int size;
			if (val == Integer.MIN_VALUE) {
				size = "-2147483648".length();
			} else {
				size = (val < 0) ? IOUtils.stringSize(-val) + 1 : IOUtils.stringSize(val);
			}
			sizeArray[i] = size;
			totalSize += size;
		}

		int newcount = count + totalSize;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		buf[count] = '[';

		int currentSize = count + 1;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				buf[currentSize++] = ',';
			}

			int val = array[i];
			if (val == Integer.MIN_VALUE) {
				System.arraycopy("-2147483648".toCharArray(), 0, buf, currentSize, sizeArray[i]);
				currentSize += sizeArray[i];
			} else {
				currentSize += sizeArray[i];
				IOUtils.getChars(val, currentSize, buf);
			}
		}
		buf[currentSize] = ']';

		count = newcount;
	}

	public void writeIntArray(Integer[] array) throws IOException {
		int[] sizeArray = new int[array.length];
		int totalSize = 2;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				totalSize++;
			}
			Integer val = array[i];
			int size;
			if (val == null) {
				size = "null".length();
			} else if (val.intValue() == Integer.MIN_VALUE) {
				size = "-2147483648".length();
			} else {
				size = (val < 0) ? IOUtils.stringSize(-val) + 1 : IOUtils.stringSize(val);
			}
			sizeArray[i] = size;
			totalSize += size;
		}

		int newcount = count + totalSize;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		buf[count] = '[';

		int currentSize = count + 1;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				buf[currentSize++] = ',';
			}

			Integer val = array[i];
			if (val == null) {
				System.arraycopy("null".toCharArray(), 0, buf, currentSize, sizeArray[i]);
				currentSize += sizeArray[i];
			} else if (val == Integer.MIN_VALUE) {
				System.arraycopy("-2147483648".toCharArray(), 0, buf, currentSize, sizeArray[i]);
				currentSize += sizeArray[i];
			} else {
				currentSize += sizeArray[i];
				IOUtils.getChars(val, currentSize, buf);
			}
		}
		buf[currentSize] = ']';

		count = newcount;
	}

	public void writeIntAndChar(int i, char c) throws IOException {
		if (i == Integer.MIN_VALUE) {
			write("-2147483648");
			write(c);
			return;
		}

		int size = (i < 0) ? IOUtils.stringSize(-i) + 1 : IOUtils.stringSize(i);

		int newcount0 = count + size;
		int newcount1 = newcount0 + 1;

		if (newcount1 > buf.length) {
			expandCapacity(newcount1);
		}

		IOUtils.getChars(i, newcount0, buf);
		buf[newcount0] = c;

		count = newcount1;
	}

	public void writeLongAndChar(long i, char c) throws IOException {
		if (i == Long.MIN_VALUE) {
			write("-9223372036854775808");
			write(c);
			return;
		}

		int size = (i < 0) ? IOUtils.stringSize(-i) + 1 : IOUtils.stringSize(i);

		int newcount0 = count + size;
		int newcount1 = newcount0 + 1;

		if (newcount1 > buf.length) {
			expandCapacity(newcount1);
		}

		IOUtils.getChars(i, newcount0, buf);
		buf[newcount0] = c;

		count = newcount1;
	}

	public void writeLong(long i) throws IOException {
		if (i == Long.MIN_VALUE) {
			write("-9223372036854775808");
			return;
		}

		int size = (i < 0) ? IOUtils.stringSize(-i) + 1 : IOUtils.stringSize(i);

		int newcount = count + size;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		IOUtils.getChars(i, newcount, buf);

		count = newcount;
	}
	
	public void writeLongArray(long[] array) throws IOException {
		int[] sizeArray = new int[array.length];
		int totalSize = 2;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				totalSize++;
			}
			long val = array[i];
			int size;
			if (val == Long.MIN_VALUE) {
				size = "-9223372036854775808".length();
			} else {
				size = (val < 0) ? IOUtils.stringSize(-val) + 1 : IOUtils.stringSize(val);
			}
			sizeArray[i] = size;
			totalSize += size;
		}

		int newcount = count + totalSize;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		buf[count] = '[';

		int currentSize = count + 1;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				buf[currentSize++] = ',';
			}

			long val = array[i];
			if (val == Long.MIN_VALUE) {
				System.arraycopy("-9223372036854775808".toCharArray(), 0, buf, currentSize, sizeArray[i]);
				currentSize += sizeArray[i];
			} else {
				currentSize += sizeArray[i];
				IOUtils.getChars(val, currentSize, buf);
			}
		}
		buf[currentSize] = ']';

		count = newcount;
	}

	public void writeStringWithDoubleQuote(String text) {
		//final boolean[] specicalFlags_doubleQuotes = CharTypes.specicalFlags_doubleQuotes;
		//final int len_flags = specicalFlags_doubleQuotes.length;
		
		if (text == null) {
			int newcount = count + 4;
			if (newcount > buf.length) {
				expandCapacity(newcount);
			}
			"null".getChars(0, 4, buf, count);
			count = newcount;
			return;
		}

		int len = text.length();
		int newcount = count + len + 2;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		int start = count + 1;
		int end = start + len;

		buf[count] = '\"';
		text.getChars(0, len, buf, start);

		count = newcount;

		for (int i = start; i < end; ++i) {
			char ch = buf[i];
			if (ch == '\b' || ch == '\n' || ch == '\r' || ch == '\f' || ch == '\\' || ch == '/' || ch == '"') {
			//if (ch < len_flags && specicalFlags_doubleQuotes[ch]) {
				newcount++;
				if (newcount > buf.length) {
					expandCapacity(newcount);
				}
				count = newcount;

				System.arraycopy(buf, i + 1, buf, i + 2, end - i - 1);
				buf[i] = '\\';
				buf[++i] = replaceChars[(int) ch];
				end++;
			}
		}

		buf[newcount - 1] = '\"';
	}
	
	public void writeKeyWithDoubleQuote(String text) throws IOException {
		final boolean[] specicalFlags_doubleQuotes = CharTypes.specicalFlags_doubleQuotes;
		
		int len = text.length();
		int newcount = count + len + 3;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		int start = count + 1;
		int end = start + len;

		buf[count] = '\"';
		text.getChars(0, len, buf, start);

		count = newcount;

		for (int i = start; i < end; ++i) {
			char ch = buf[i];
			if (ch < specicalFlags_doubleQuotes.length && specicalFlags_doubleQuotes[ch]) {
				newcount++;
				if (newcount > buf.length) {
					expandCapacity(newcount);
				}
				count = newcount;

				System.arraycopy(buf, i + 1, buf, i + 2, end - i - 1);
				buf[i] = '\\';
				buf[++i] = replaceChars[(int) ch];
				end++;
			}
		}

		buf[newcount - 2] = '\"';
		buf[newcount - 1] = ':';
	}

	public void writeStringWithSingleQuote(String text) throws IOException {
		final boolean[] specicalFlags_singleQuotes = CharTypes.specicalFlags_singleQuotes;
		
		if (text == null) {
			int newcount = count + 4;
			if (newcount > buf.length) {
				expandCapacity(newcount);
			}
			"null".getChars(0, 4, buf, count);
			count = newcount;
			return;
		}

		int len = text.length();
		int newcount = count + len + 2;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		int start = count + 1;
		int end = start + len;

		buf[count] = '\'';
		text.getChars(0, len, buf, start);
		count = newcount;

		for (int i = start; i < end; ++i) {
			char ch = buf[i];
			if (ch < specicalFlags_singleQuotes.length && specicalFlags_singleQuotes[ch]) {
				newcount++;
				if (newcount > buf.length) {
					expandCapacity(newcount);
				}
				count = newcount;

				System.arraycopy(buf, i + 1, buf, i + 2, end - i - 1);
				buf[i] = '\\';
				buf[++i] = replaceChars[(int) ch];
				end++;
			}
		}
		
		buf[newcount - 1] = '\'';
	}
	
	public void writeStringArray(String[] array) throws IOException {
		int totalSize = 2;
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				totalSize++;
			}
			String val = array[i];
			int size;
			if (val == null) {
				size = 4;
			} else {
				size = val.length() + 2;
			}
			totalSize += size;
		}

		
		
		int currentSize = count;
		
		int newcount = count + totalSize;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		count = newcount;
		
		buf[currentSize++] = '[';
		for (int i = 0; i < array.length; ++i) {
			if (i != 0) {
				buf[currentSize++] = ',';
			}

			String val = array[i];
			if (val == null) {
				buf[currentSize++] = 'n';
				buf[currentSize++] = 'u';
				buf[currentSize++] = 'l';
				buf[currentSize++] = 'l';
			} else {
				
				buf[currentSize++] = '"';
				int start = currentSize;
				int len = val.length();
				val.getChars(0, len, buf, currentSize);
				currentSize += len;
				int end = currentSize;
				buf[currentSize++] = '"';
				
				for (int j = start; j < end; ++j) {
					char ch = buf[j];
					if (ch == '\b' || ch == '\n' || ch == '\r' || ch == '\f' || ch == '\\' || ch == '/' || ch == '"') {
					//if (ch < specicalFlags_singleQuotes.length && specicalFlags_singleQuotes[ch]) {
						newcount++;
						currentSize++;
						if (newcount > buf.length) {
							expandCapacity(newcount);
						}
						count = newcount;

						System.arraycopy(buf, j + 1, buf, j + 2, end - j);
						buf[j] = '\\';
						buf[++j] = replaceChars[(int) ch];
						end++;
					}
				}
			}
		}
		buf[currentSize] = ']';

		
	}
	
	public void writeKeyWithSingleQuote(String text) throws IOException {
		final boolean[] specicalFlags_singleQuotes = CharTypes.specicalFlags_singleQuotes;
		
		int len = text.length();
		int newcount = count + len + 3;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		int start = count + 1;
		int end = start + len;

		buf[count] = '\'';
		text.getChars(0, len, buf, start);
		count = newcount;

		for (int i = start; i < end; ++i) {
			char ch = buf[i];
			if (ch < specicalFlags_singleQuotes.length && specicalFlags_singleQuotes[ch]) {
				newcount++;
				if (newcount > buf.length) {
					expandCapacity(newcount);
				}
				count = newcount;

				System.arraycopy(buf, i + 1, buf, i + 2, end - i - 1);
				buf[i] = '\\';
				buf[++i] = replaceChars[(int) ch];
				end++;
			}
		}
		
		buf[newcount - 2] = '\'';
		buf[newcount - 1] = ':';
	}

	public void writeKeyWithDoubleQuoteIfHashSpecial(String text) throws IOException {
		final boolean[] specicalFlags_doubleQuotes = CharTypes.specicalFlags_doubleQuotes;
		
		int len = text.length();
		int newcount = count + len + 1;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		int start = count;
		int end = start + len;

		text.getChars(0, len, buf, start);
		count = newcount;

		boolean hashSpecial = false;

		for (int i = start; i < end; ++i) {
			char ch = buf[i];
			if (ch < specicalFlags_doubleQuotes.length && specicalFlags_doubleQuotes[ch]) {
				if (!hashSpecial) {
					newcount += 3;
					if (newcount > buf.length) {
						expandCapacity(newcount);
					}
					count = newcount;
					
					System.arraycopy(buf, i + 1, buf, i + 3, end - i - 1);
					System.arraycopy(buf, 0, buf, 1, i);
					buf[start] = '"';
					buf[++i] = '\\';
					buf[++i] = replaceChars[(int) ch];
					end+=2;
					buf[count - 2] = '"';
					
					hashSpecial = true;
				} else {
					newcount++;
					if (newcount > buf.length) {
						expandCapacity(newcount);
					}
					count = newcount;
					
					System.arraycopy(buf, i + 1, buf, i + 2, end - i);
					buf[i] = '\\';
					buf[++i] = replaceChars[(int) ch];
					end++;
				}
			}
		}
		
		buf[newcount - 1] = ':';
	}
	
	public void writeKeyWithSingleQuoteIfHashSpecial(String text) throws IOException {
		final boolean[] specicalFlags_singleQuotes = CharTypes.specicalFlags_singleQuotes;
		
		int len = text.length();
		int newcount = count + len + 1;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		int start = count;
		int end = start + len;

		text.getChars(0, len, buf, start);
		count = newcount;

		boolean hashSpecial = false;

		for (int i = start; i < end; ++i) {
			char ch = buf[i];
			if (ch < specicalFlags_singleQuotes.length && specicalFlags_singleQuotes[ch]) {
				if (!hashSpecial) {
					newcount += 3;
					if (newcount > buf.length) {
						expandCapacity(newcount);
					}
					count = newcount;
					
					System.arraycopy(buf, i + 1, buf, i + 3, end - i - 1);
					System.arraycopy(buf, 0, buf, 1, i);
					buf[start] = '\'';
					buf[++i] = '\\';
					buf[++i] = replaceChars[(int) ch];
					end+=2;
					buf[count - 2] = '\'';
					
					hashSpecial = true;
				} else {
					newcount++;
					if (newcount > buf.length) {
						expandCapacity(newcount);
					}
					count = newcount;
					
					System.arraycopy(buf, i + 1, buf, i + 2, end - i);
					buf[i] = '\\';
					buf[++i] = replaceChars[(int) ch];
					end++;
				}
			}
		}
		
		buf[newcount - 1] = ':';
	}
}
